/****************************************************************************
 * Copyright (c) 2015 ~ 2023 liangxiegame MIT License
 *
 * QFramework v1.0
 *
 * https://qframework.cn
 * https://github.com/liangxiegame/QFramework
 * https://gitee.com/liangxiegame/QFramework
 *
 * Author:
 *  liangxie        https://github.com/liangxie
 *  soso            https://github.com/so-sos-so
 *
 * Contributor
 *  TastSong        https://github.com/TastSong
 *  京产肠饭         https://gitee.com/JingChanChangFan/hk_-unity-tools
 *  猫叔(一只皮皮虾)  https://space.bilibili.com/656352/
 *  New一天
 *  幽飞冷凝雪～冷 
 *
 * Community
 *  QQ Group: 623597263
 * Latest Update: 2023.9.26 15:14 support godot4.net
 ****************************************************************************/

using System;
using System.Collections.Generic;

namespace QFramework
{
	#region Architecture

	public interface IArchitecture
	{
		void RegisterSystem<T>(T system) where T : ISystem;

		void RegisterModel<T>(T model) where T : IModel;

		void RegisterUtility<T>(T utility) where T : IUtility;

		T GetSystem<T>() where T : class, ISystem;

		T GetModel<T>() where T : class, IModel;

		T GetUtility<T>() where T : class, IUtility;

		void SendCommand<T>(T command) where T : ICommand;

		TResult SendCommand<TResult>(ICommand<TResult> command);

		TResult SendQuery<TResult>(IQuery<TResult> query);

		void SendEvent<T>() where T : new();
		void SendEvent<T>(T e);

		IUnRegister RegisterEvent<T>(Action<T> onEvent);
		void UnRegisterEvent<T>(Action<T> onEvent);
	}

	public abstract class Architecture<T> : IArchitecture where T : Architecture<T>, new()
	{
		private bool mInited = false;

		private HashSet<ISystem> mSystems = new HashSet<ISystem>();

		private HashSet<IModel> mModels = new HashSet<IModel>();

		public static Action<T> OnRegisterPatch = architecture => { };

		protected static T mArchitecture;

		public static IArchitecture Interface
		{
			get
			{
				if (mArchitecture == null)
				{
					MakeSureArchitecture();
				}

				return mArchitecture;
			}
		}


		static void MakeSureArchitecture()
		{
			if (mArchitecture == null)
			{
				mArchitecture = new T();
				mArchitecture.Init();

				OnRegisterPatch?.Invoke(mArchitecture);

				foreach (var architectureModel in mArchitecture.mModels)
				{
					architectureModel.Init();
				}

				mArchitecture.mModels.Clear();

				foreach (var architectureSystem in mArchitecture.mSystems)
				{
					architectureSystem.Init();
				}

				mArchitecture.mSystems.Clear();

				mArchitecture.mInited = true;
			}
		}

		protected abstract void Init();

		private IOCContainer mContainer = new IOCContainer();

		public void RegisterSystem<TSystem>(TSystem system) where TSystem : ISystem
		{
			system.SetArchitecture(this);
			mContainer.Register<TSystem>(system);

			if (!mInited)
			{
				mSystems.Add(system);
			}
			else
			{
				system.Init();
			}
		}

		public void RegisterModel<TModel>(TModel model) where TModel : IModel
		{
			model.SetArchitecture(this);
			mContainer.Register<TModel>(model);

			if (!mInited)
			{
				mModels.Add(model);
			}
			else
			{
				model.Init();
			}
		}

		public void RegisterUtility<TUtility>(TUtility utility) where TUtility : IUtility =>
			mContainer.Register<TUtility>(utility);

		public TSystem GetSystem<TSystem>() where TSystem : class, ISystem => mContainer.Get<TSystem>();

		public TModel GetModel<TModel>() where TModel : class, IModel => mContainer.Get<TModel>();

		public TUtility GetUtility<TUtility>() where TUtility : class, IUtility => mContainer.Get<TUtility>();

		public TResult SendCommand<TResult>(ICommand<TResult> command) => ExecuteCommand(command);

		public void SendCommand<TCommand>(TCommand command) where TCommand : ICommand => ExecuteCommand(command);

		protected virtual TResult ExecuteCommand<TResult>(ICommand<TResult> command)
		{
			command.SetArchitecture(this);
			return command.Execute();
		}

		protected virtual void ExecuteCommand(ICommand command)
		{
			command.SetArchitecture(this);
			command.Execute();
		}

		public TResult SendQuery<TResult>(IQuery<TResult> query) => DoQuery<TResult>(query);

		protected virtual TResult DoQuery<TResult>(IQuery<TResult> query)
		{
			query.SetArchitecture(this);
			return query.Do();
		}

		private TypeEventSystem mTypeEventSystem = new TypeEventSystem();

		public void SendEvent<TEvent>() where TEvent : new() => mTypeEventSystem.Send<TEvent>();

		public void SendEvent<TEvent>(TEvent e) => mTypeEventSystem.Send<TEvent>(e);

		public IUnRegister RegisterEvent<TEvent>(Action<TEvent> onEvent) => mTypeEventSystem.Register<TEvent>(onEvent);

		public void UnRegisterEvent<TEvent>(Action<TEvent> onEvent) => mTypeEventSystem.UnRegister<TEvent>(onEvent);
	}

	public interface IOnEvent<T>
	{
		void OnEvent(T e);
	}

	public static class OnGlobalEventExtension
	{
		public static IUnRegister RegisterEvent<T>(this IOnEvent<T> self) where T : struct =>
			TypeEventSystem.Global.Register<T>(self.OnEvent);

		public static void UnRegisterEvent<T>(this IOnEvent<T> self) where T : struct =>
			TypeEventSystem.Global.UnRegister<T>(self.OnEvent);
	}

	#endregion

	#region Controller

	public interface IController : IBelongToArchitecture, ICanSendCommand, ICanGetSystem, ICanGetModel,
		ICanRegisterEvent, ICanSendQuery, ICanGetUtility
	{
	}

	#endregion

	#region System

	public interface ISystem : IBelongToArchitecture, ICanSetArchitecture, ICanGetModel, ICanGetUtility,
		ICanRegisterEvent, ICanSendEvent, ICanGetSystem
	{
		void Init();
	}

	public abstract class AbstractSystem : ISystem
	{
		private IArchitecture mArchitecture;

		IArchitecture IBelongToArchitecture.GetArchitecture() => mArchitecture;

		void ICanSetArchitecture.SetArchitecture(IArchitecture architecture) => mArchitecture = architecture;

		void ISystem.Init() => OnInit();

		protected abstract void OnInit();
	}

	#endregion

	#region Model

	public interface IModel : IBelongToArchitecture, ICanSetArchitecture, ICanGetUtility, ICanSendEvent
	{
		void Init();
	}

	public abstract class AbstractModel : IModel
	{
		private IArchitecture mArchitecturel;

		IArchitecture IBelongToArchitecture.GetArchitecture() => mArchitecturel;

		void ICanSetArchitecture.SetArchitecture(IArchitecture architecture) => mArchitecturel = architecture;

		void IModel.Init() => OnInit();

		protected abstract void OnInit();
	}

	#endregion

	#region Utility

	public interface IUtility
	{
	}

	#endregion

	#region Command

	public interface ICommand : IBelongToArchitecture, ICanSetArchitecture, ICanGetSystem, ICanGetModel, ICanGetUtility,
		ICanSendEvent, ICanSendCommand, ICanSendQuery
	{
		void Execute();
	}

	public interface ICommand<TResult> : IBelongToArchitecture, ICanSetArchitecture, ICanGetSystem, ICanGetModel,
		ICanGetUtility,
		ICanSendEvent, ICanSendCommand, ICanSendQuery
	{
		TResult Execute();
	}

	public abstract class AbstractCommand : ICommand
	{
		private IArchitecture mArchitecture;

		IArchitecture IBelongToArchitecture.GetArchitecture() => mArchitecture;

		void ICanSetArchitecture.SetArchitecture(IArchitecture architecture) => mArchitecture = architecture;

		void ICommand.Execute() => OnExecute();

		protected abstract void OnExecute();
	}

	public abstract class AbstractCommand<TResult> : ICommand<TResult>
	{
		private IArchitecture mArchitecture;

		IArchitecture IBelongToArchitecture.GetArchitecture() => mArchitecture;

		void ICanSetArchitecture.SetArchitecture(IArchitecture architecture) => mArchitecture = architecture;

		TResult ICommand<TResult>.Execute() => OnExecute();

		protected abstract TResult OnExecute();
	}

	#endregion

	#region Query

	public interface IQuery<TResult> : IBelongToArchitecture, ICanSetArchitecture, ICanGetModel, ICanGetSystem,
		ICanSendQuery
	{
		TResult Do();
	}

	public abstract class AbstractQuery<T> : IQuery<T>
	{
		public T Do() => OnDo();

		protected abstract T OnDo();


		private IArchitecture mArchitecture;

		public IArchitecture GetArchitecture() => mArchitecture;

		public void SetArchitecture(IArchitecture architecture) => mArchitecture = architecture;
	}

	#endregion

	#region Rule

	public interface IBelongToArchitecture
	{
		IArchitecture GetArchitecture();
	}

	public interface ICanSetArchitecture
	{
		void SetArchitecture(IArchitecture architecture);
	}

	public interface ICanGetModel : IBelongToArchitecture
	{
	}

	public static class CanGetModelExtension
	{
		public static T GetModel<T>(this ICanGetModel self) where T : class, IModel =>
			self.GetArchitecture().GetModel<T>();
	}

	public interface ICanGetSystem : IBelongToArchitecture
	{
	}

	public static class CanGetSystemExtension
	{
		public static T GetSystem<T>(this ICanGetSystem self) where T : class, ISystem =>
			self.GetArchitecture().GetSystem<T>();
	}

	public interface ICanGetUtility : IBelongToArchitecture
	{
	}

	public static class CanGetUtilityExtension
	{
		public static T GetUtility<T>(this ICanGetUtility self) where T : class, IUtility =>
			self.GetArchitecture().GetUtility<T>();
	}

	public interface ICanRegisterEvent : IBelongToArchitecture
	{
	}

	public static class CanRegisterEventExtension
	{
		public static IUnRegister RegisterEvent<T>(this ICanRegisterEvent self, Action<T> onEvent) =>
			self.GetArchitecture().RegisterEvent<T>(onEvent);

		public static void UnRegisterEvent<T>(this ICanRegisterEvent self, Action<T> onEvent) =>
			self.GetArchitecture().UnRegisterEvent<T>(onEvent);
	}

	public interface ICanSendCommand : IBelongToArchitecture
	{
	}

	public static class CanSendCommandExtension
	{
		public static void SendCommand<T>(this ICanSendCommand self) where T : ICommand, new() =>
			self.GetArchitecture().SendCommand<T>(new T());

		public static void SendCommand<T>(this ICanSendCommand self, T command) where T : ICommand =>
			self.GetArchitecture().SendCommand<T>(command);

		public static TResult SendCommand<TResult>(this ICanSendCommand self, ICommand<TResult> command) =>
			self.GetArchitecture().SendCommand(command);
	}

	public interface ICanSendEvent : IBelongToArchitecture
	{
	}

	public static class CanSendEventExtension
	{
		public static void SendEvent<T>(this ICanSendEvent self) where T : new() =>
			self.GetArchitecture().SendEvent<T>();

		public static void SendEvent<T>(this ICanSendEvent self, T e) => self.GetArchitecture().SendEvent<T>(e);
	}

	public interface ICanSendQuery : IBelongToArchitecture
	{
	}

	public static class CanSendQueryExtension
	{
		public static TResult SendQuery<TResult>(this ICanSendQuery self, IQuery<TResult> query) =>
			self.GetArchitecture().SendQuery(query);
	}

	#endregion

	#region TypeEventSystem

	public interface IUnRegister
	{
		void UnRegister();
	}

	public interface IUnRegisterList
	{
		List<IUnRegister> UnregisterList { get; }
	}

	public static class IUnRegisterListExtension
	{
		public static void AddToUnregisterList(this IUnRegister self, IUnRegisterList unRegisterList) =>
			unRegisterList.UnregisterList.Add(self);

		public static void UnRegisterAll(this IUnRegisterList self)
		{
			foreach (var unRegister in self.UnregisterList)
			{
				unRegister.UnRegister();
			}

			self.UnregisterList.Clear();
		}
	}

	public struct CustomUnRegister : IUnRegister
	{
		private Action mOnUnRegister { get; set; }
		public CustomUnRegister(Action onUnRegister) => mOnUnRegister = onUnRegister;

		public void UnRegister()
		{
			mOnUnRegister.Invoke();
			mOnUnRegister = null;
		}
	}

	#if UNITY_5_6_OR_NEWER
	public class UnRegisterOnDestroyTrigger : UnityEngine.MonoBehaviour
	{
		private readonly HashSet<IUnRegister> mUnRegisters = new HashSet<IUnRegister>();

		public void AddUnRegister(IUnRegister unRegister) => mUnRegisters.Add(unRegister);

		public void RemoveUnRegister(IUnRegister unRegister) => mUnRegisters.Remove(unRegister);

		private void OnDestroy()
		{
			foreach (var unRegister in mUnRegisters)
			{
				unRegister.UnRegister();
			}

			mUnRegisters.Clear();
		}
	}
	#endif

	public static class UnRegisterExtension
	{
		#if UNITY_5_6_OR_NEWER
		public static IUnRegister UnRegisterWhenGameObjectDestroyed(this IUnRegister unRegister, UnityEngine.GameObject gameObject)
		{
			var trigger = gameObject.GetComponent<UnRegisterOnDestroyTrigger>();

			if (!trigger)
			{
				trigger = gameObject.AddComponent<UnRegisterOnDestroyTrigger>();
			}

			trigger.AddUnRegister(unRegister);

			return unRegister;
		}

		public static IUnRegister UnRegisterWhenGameObjectDestroyed<T>(this IUnRegister self, T component)
			where T : UnityEngine.Component =>
			self.UnRegisterWhenGameObjectDestroyed(component.gameObject);
		#endif
		
		
#if GODOT
		public static IUnRegister UnRegisterWhenNodeExitTree(this IUnRegister unRegister, Godot.Node node)
		{
			node.TreeExiting += unRegister.UnRegister;
			return unRegister;
		}
#endif
	}

	public class TypeEventSystem
	{
		private readonly EasyEvents mEvents = new EasyEvents();

		public static readonly TypeEventSystem Global = new TypeEventSystem();

		public void Send<T>() where T : new() => mEvents.GetEvent<EasyEvent<T>>()?.Trigger(new T());

		public void Send<T>(T e) => mEvents.GetEvent<EasyEvent<T>>()?.Trigger(e);

		public IUnRegister Register<T>(Action<T> onEvent) => mEvents.GetOrAddEvent<EasyEvent<T>>().Register(onEvent);

		public void UnRegister<T>(Action<T> onEvent)
		{
			var e = mEvents.GetEvent<EasyEvent<T>>();
			if (e != null)
			{
				e.UnRegister(onEvent);
			}
		}
	}

	#endregion

	#region IOC

	public class IOCContainer
	{
		private Dictionary<Type, object> mInstances = new Dictionary<Type, object>();

		public void Register<T>(T instance)
		{
			var key = typeof(T);

			if (mInstances.ContainsKey(key))
			{
				mInstances[key] = instance;
			}
			else
			{
				mInstances.Add(key, instance);
			}
		}

		public T Get<T>() where T : class
		{
			var key = typeof(T);

			if (mInstances.TryGetValue(key, out var retInstance))
			{
				return retInstance as T;
			}

			return null;
		}
	}

	#endregion

	#region BindableProperty

	public interface IBindableProperty<T> : IReadonlyBindableProperty<T>
	{
		new T Value { get; set; }
		void SetValueWithoutEvent(T newValue);
	}

	public interface IReadonlyBindableProperty<T> : IEasyEvent
	{
		T Value { get; }

		IUnRegister RegisterWithInitValue(Action<T> action);
		void UnRegister(Action<T> onValueChanged);
		IUnRegister Register(Action<T> onValueChanged);
	}

	public class BindableProperty<T> : IBindableProperty<T>
	{
		public BindableProperty(T defaultValue = default) => mValue = defaultValue;

		protected T mValue;

		public static Func<T, T, bool> Comparer { get; set; } = (a, b) => a.Equals(b);

		public BindableProperty<T> WithComparer(Func<T, T, bool> comparer)
		{
			Comparer = comparer;
			return this;
		}

		public T Value
		{
			get => GetValue();
			set
			{
				if (value == null && mValue == null) return;
				if (value != null && Comparer(value, mValue)) return;

				SetValue(value);
				mOnValueChanged?.Invoke(value);
			}
		}

		protected virtual void SetValue(T newValue) => mValue = newValue;

		protected virtual T GetValue() => mValue;

		public void SetValueWithoutEvent(T newValue) => mValue = newValue;

		private Action<T> mOnValueChanged = (v) => { };

		public IUnRegister Register(Action<T> onValueChanged)
		{
			mOnValueChanged += onValueChanged;
			return new BindablePropertyUnRegister<T>(this, onValueChanged);
		}

		public IUnRegister RegisterWithInitValue(Action<T> onValueChanged)
		{
			onValueChanged(mValue);
			return Register(onValueChanged);
		}

		public void UnRegister(Action<T> onValueChanged) => mOnValueChanged -= onValueChanged;

		IUnRegister IEasyEvent.Register(Action onEvent)
		{
			return Register(Action);
			void Action(T _) => onEvent();
		}
		
		public override string ToString() => Value.ToString();
	}

	internal class ComparerAutoRegister
	{
		#if UNITY_5_6_OR_NEWER
		[UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.BeforeSceneLoad)]
		public static void AutoRegister()
		{
			BindableProperty<int>.Comparer = (a, b) => a == b;
			BindableProperty<float>.Comparer = (a, b) => a == b;
			BindableProperty<double>.Comparer = (a, b) => a == b;
			BindableProperty<string>.Comparer = (a, b) => a == b;
			BindableProperty<long>.Comparer = (a, b) => a == b;
			BindableProperty<UnityEngine.Vector2>.Comparer = (a, b) => a == b;
			BindableProperty<UnityEngine.Vector3>.Comparer = (a, b) => a == b;
			BindableProperty<UnityEngine.Vector4>.Comparer = (a, b) => a == b;
			BindableProperty<UnityEngine.Color>.Comparer = (a, b) => a == b;
			BindableProperty<UnityEngine.Color32>.Comparer = (a, b) => a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a;
			BindableProperty<UnityEngine.Bounds>.Comparer = (a, b) => a == b;
			BindableProperty<UnityEngine.Rect>.Comparer = (a, b) => a == b;
			BindableProperty<UnityEngine.Quaternion>.Comparer = (a, b) => a == b;
			BindableProperty<UnityEngine.Vector2Int>.Comparer = (a, b) => a == b;
			BindableProperty<UnityEngine.Vector3Int>.Comparer = (a, b) => a == b;
			BindableProperty<UnityEngine.BoundsInt>.Comparer = (a, b) => a == b;
			BindableProperty<UnityEngine.RangeInt>.Comparer = (a, b) => a.start == b.start && a.length == b.length;
			BindableProperty<UnityEngine.RectInt>.Comparer = (a, b) => a.Equals(b);
		}
		#endif
	}

	public class BindablePropertyUnRegister<T> : IUnRegister
	{
		public BindablePropertyUnRegister(BindableProperty<T> bindableProperty, Action<T> onValueChanged)
		{
			BindableProperty = bindableProperty;
			OnValueChanged = onValueChanged;
		}

		public BindableProperty<T> BindableProperty { get; set; }

		public Action<T> OnValueChanged { get; set; }

		public void UnRegister()
		{
			BindableProperty.UnRegister(OnValueChanged);
			BindableProperty = null;
			OnValueChanged = null;
		}
	}

	#endregion

	#region EasyEvent

	public interface IEasyEvent
	{
		IUnRegister Register(Action onEvent);
	}

	public class EasyEvent : IEasyEvent
	{
		private Action mOnEvent = () => { };

		public IUnRegister Register(Action onEvent)
		{
			mOnEvent += onEvent;
			return new CustomUnRegister(() => { UnRegister(onEvent); });
		}

		public void UnRegister(Action onEvent) => mOnEvent -= onEvent;

		public void Trigger() => mOnEvent?.Invoke();
	}

	public class EasyEvent<T> : IEasyEvent
	{
		private Action<T> mOnEvent = e => { };

		public IUnRegister Register(Action<T> onEvent)
		{
			mOnEvent += onEvent;
			return new CustomUnRegister(() => { UnRegister(onEvent); });
		}

		public void UnRegister(Action<T> onEvent) => mOnEvent -= onEvent;

		public void Trigger(T t) => mOnEvent?.Invoke(t);

		IUnRegister IEasyEvent.Register(Action onEvent)
		{
			return Register(Action);
			void Action(T _) => onEvent();
		}
	}

	public class EasyEvent<T, K> : IEasyEvent
	{
		private Action<T, K> mOnEvent = (t, k) => { };

		public IUnRegister Register(Action<T, K> onEvent)
		{
			mOnEvent += onEvent;
			return new CustomUnRegister(() => { UnRegister(onEvent); });
		}

		public void UnRegister(Action<T, K> onEvent) => mOnEvent -= onEvent;

		public void Trigger(T t, K k) => mOnEvent?.Invoke(t, k);

		IUnRegister IEasyEvent.Register(Action onEvent)
		{
			return Register(Action);
			void Action(T _, K __) => onEvent();
		}
	}

	public class EasyEvent<T, K, S> : IEasyEvent
	{
		private Action<T, K, S> mOnEvent = (t, k, s) => { };

		public IUnRegister Register(Action<T, K, S> onEvent)
		{
			mOnEvent += onEvent;
			return new CustomUnRegister(() => { UnRegister(onEvent); });
		}

		public void UnRegister(Action<T, K, S> onEvent) => mOnEvent -= onEvent;

		public void Trigger(T t, K k, S s) => mOnEvent?.Invoke(t, k, s);

		IUnRegister IEasyEvent.Register(Action onEvent)
		{
			return Register(Action);
			void Action(T _, K __, S ___) => onEvent();
		}
	}

	public class EasyEvents
	{
		private static readonly EasyEvents mGlobalEvents = new EasyEvents();

		public static T Get<T>() where T : IEasyEvent => mGlobalEvents.GetEvent<T>();

		public static void Register<T>() where T : IEasyEvent, new() => mGlobalEvents.AddEvent<T>();

		private readonly Dictionary<Type, IEasyEvent> mTypeEvents = new Dictionary<Type, IEasyEvent>();

		public void AddEvent<T>() where T : IEasyEvent, new() => mTypeEvents.Add(typeof(T), new T());

		public T GetEvent<T>() where T : IEasyEvent
		{
			return mTypeEvents.TryGetValue(typeof(T), out var e) ? (T)e : default;
		}

		public T GetOrAddEvent<T>() where T : IEasyEvent, new()
		{
			var eType = typeof(T);
			if (mTypeEvents.TryGetValue(eType, out var e))
			{
				return (T)e;
			}

			var t = new T();
			mTypeEvents.Add(eType, t);
			return t;
		}
	}

	#endregion


	#region Event Extension

	public class OrEvent : IUnRegisterList
	{
		public OrEvent Or(IEasyEvent easyEvent)
		{
			easyEvent.Register(Trigger).AddToUnregisterList(this);
			return this;
		}

		private Action mOnEvent = () => { };

		public IUnRegister Register(Action onEvent)
		{
			mOnEvent += onEvent;
			return new CustomUnRegister(() => { UnRegister(onEvent); });
		}

		public void UnRegister(Action onEvent)
		{
			mOnEvent -= onEvent;
			this.UnRegisterAll();
		}

		private void Trigger() => mOnEvent?.Invoke();

		public List<IUnRegister> UnregisterList { get; } = new List<IUnRegister>();
	}

	public static class OrEventExtensions
	{
		public static OrEvent Or(this IEasyEvent self, IEasyEvent e) => new OrEvent().Or(self).Or(e);
	}

	#endregion

#if UNITY_EDITOR
	internal class EditorMenus
	{
		[UnityEditor.MenuItem("QFramework/Install QFrameworkWithToolKits")]
		public static void InstallPackageKit() => UnityEngine.Application.OpenURL("https://qframework.cn/qf");
	}
#endif
}
